package org.zjvis.datascience.service;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.*;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zjvis.datascience.common.constant.DatabaseConstant;
import org.zjvis.datascience.common.dto.DatasetAndCategoryDTO;
import org.zjvis.datascience.common.dto.DatasetCategoryDTO;
import org.zjvis.datascience.common.dto.DatasetDTO;
import org.zjvis.datascience.common.dto.ProjectDTO;
import org.zjvis.datascience.common.dto.dataset.DatasetNameStatusDTO;
import org.zjvis.datascience.common.dto.dataset.DatasetNameTypeDTO;
import org.zjvis.datascience.common.dto.dataset.DatasetUsedInProjectDTO;
import org.zjvis.datascience.common.exception.BaseErrorCode;
import org.zjvis.datascience.common.exception.DataScienceException;
import org.zjvis.datascience.common.util.DozerUtil;
import org.zjvis.datascience.common.util.JwtUtil;
import org.zjvis.datascience.common.util.SqlUtil;
import org.zjvis.datascience.common.util.db.JDBCUtil;
import org.zjvis.datascience.common.vo.category.DatasetAndCategoryForProjectVO;
import org.zjvis.datascience.common.vo.category.DatasetAndCategoryVO;
import org.zjvis.datascience.common.vo.category.SearchDatasetAndCategoryVO;
import org.zjvis.datascience.common.vo.dataset.QueryDatasetUsedInProjectVO;
import org.zjvis.datascience.service.dataprovider.GPDataProvider;
import org.zjvis.datascience.service.kg.KnowledgeGraphService;
import org.zjvis.datascience.service.mapper.DatasetCategoryMapper;
import org.zjvis.datascience.service.mapper.DatasetMapper;
import org.zjvis.datascience.service.mapper.DatasetProjectMapper;

import static org.zjvis.datascience.common.constant.DatabaseConstant.DEFAULT_DATASET_ID;
import static org.zjvis.datascience.service.graph.GraphFileService.GRAPH_FILE_DIR;

/**
 * @description DatasetCategory 数据集分类 Service
 * @date 2021-12-23
 */
@Service
public class DatasetCategoryService {

    private final static Logger logger = LoggerFactory.getLogger("DatasetCategoryService");

    @Autowired
    private DatasetCategoryMapper datasetCategoryMapper;

    @Autowired
    private DatasetProjectMapper datasetProjectMapper;

    @Autowired
    private DatasetMapper datasetMapper;

    @Autowired
    private GPDataProvider gpDataProvider;

    @Autowired
    private DatasetProjectService datasetProjectService;

    @Autowired
    private KnowledgeGraphService knowledgeGraphService;

    @Lazy
    @Autowired
    private ProjectService projectService;

    @Autowired
    private MinioService minioService;

    public static final String DEFAULT_CATEGORY = "默认分类";

    public static final String DEFAULT_GRAPH_CATEGORY = "我的图类型文件";

    public static final String DEFAULT_GIS_CATEGORY = "我的GIS文件";

    public String getDefaultGraphCategory() {
        return DEFAULT_GRAPH_CATEGORY;
    }

    /**
     * 根据用户id获取分类目录
     *
     * @param userId
     * @return
     */
    public List<DatasetCategoryDTO> queryByUserId(Long userId) {
        List<DatasetCategoryDTO> datasetCategoryList = datasetCategoryMapper.queryByUserId(userId);
        return datasetCategoryList;
    }


    public DatasetAndCategoryVO addDefaultFolder(String name) {
        DatasetCategoryDTO dcDTO = new DatasetCategoryDTO();
        dcDTO.setName(name);
        dcDTO.setUserId(JwtUtil.getCurrentUserId());
        dcDTO.setGmtCreator(JwtUtil.getCurrentUserId());
        dcDTO.setGmtModifier(JwtUtil.getCurrentUserId());
        Long cateId = save(dcDTO);
        DatasetAndCategoryVO datasetVo = new DatasetAndCategoryVO();
        datasetVo.setCategoryId(cateId);
        datasetVo.setCategoryName(dcDTO.getName());
        List<DatasetNameTypeDTO> dataset = new ArrayList<>();
        datasetVo.setDataset(dataset);
        return datasetVo;
    }

    /**
     * 根据用户id获取分类目录及其下面的数据集
     *
     * @param userId
     * @return
     */
    public List<DatasetAndCategoryVO> queryDatasetAndCategoryByUserId(Long userId) {
        Map<Long, DatasetAndCategoryVO> existCategoryMaps = Maps.newHashMap();
        List<DatasetAndCategoryDTO> allUploadDatasets = datasetCategoryMapper.queryDatasetAndCategoryByUserId(userId);
        if (allUploadDatasets.isEmpty()) {
            // 没上传过数据，加入默认分类
            DatasetAndCategoryVO defaultFolder = addDefaultFolder(DEFAULT_CATEGORY);
            existCategoryMaps.put(defaultFolder.getCategoryId(), defaultFolder);
            DatasetAndCategoryVO myGisFolder = addDefaultFolder(DEFAULT_GIS_CATEGORY);
            existCategoryMaps.put(myGisFolder.getCategoryId(), myGisFolder);
            DatasetAndCategoryVO myGraph = addDefaultFolder(DEFAULT_GRAPH_CATEGORY);
            existCategoryMaps.put(myGraph.getCategoryId(), myGraph);
            return Lists.newArrayList(existCategoryMaps.values());
        }

        try {
            allUploadDatasets.addAll(queryKGFileByUserId(userId));
        } catch (IOException ioException) {
            logger.error("query KGFile error", ioException.getMessage());
        }

        Set<Long> categoryIds = new HashSet<>();
        for (DatasetAndCategoryDTO datasetInCategory : allUploadDatasets) {
            if (categoryIds.contains(datasetInCategory.getCategoryId())) {
                //已存在就添加
                for (DatasetAndCategoryVO existCategory : existCategoryMaps.values()) {
                    if (existCategory.getCategoryId().equals(datasetInCategory.getCategoryId())) {
                        existCategory.getDataset().add(new DatasetNameTypeDTO(datasetInCategory.getDatasetId(), datasetInCategory.getDatasetName(), datasetInCategory.getType(), datasetInCategory.getGisConfig()));
                    }
                }
            } else {
                //不存在创建
                categoryIds.add(datasetInCategory.getCategoryId());
                DatasetAndCategoryVO newCategory = DozerUtil.mapper(datasetInCategory, DatasetAndCategoryVO.class);
                List<DatasetNameTypeDTO> newDatasetList = new ArrayList<>();
                if (datasetInCategory.getDatasetId() != null) {
                    newDatasetList.add(new DatasetNameTypeDTO(datasetInCategory.getDatasetId(), datasetInCategory.getDatasetName(), datasetInCategory.getType(), datasetInCategory.getGisConfig()));
                }
                newCategory.setDataset(newDatasetList);
                existCategoryMaps.put(newCategory.getCategoryId(), newCategory);
            }
        }

        Iterator<Map.Entry<Long, DatasetAndCategoryVO>> iterator = existCategoryMaps.entrySet().iterator();
        while (iterator.hasNext()) {
            DatasetAndCategoryVO category = iterator.next().getValue();
            if (category.getParentId() != null) {
                DatasetAndCategoryVO parentCategory = existCategoryMaps.get(category.getParentId());
                parentCategory.setSubCategory(Lists.newArrayList(category));
                iterator.remove();
            }
        }

        return Lists.newArrayList(existCategoryMaps.values());
    }

    public List<DatasetAndCategoryDTO> queryKGFileByUserId(Long userId) throws IOException {
        List<DatasetAndCategoryDTO> ret = new ArrayList<>();
        Map<String, String> kgRequestParams = new HashMap<>();
        kgRequestParams.put("userId", String.valueOf(userId));
        Response response = knowledgeGraphService.queryNebula(kgRequestParams, "queryByUserId");
        if (response == null || response.code() != 200) {
            return ret;
        }
        String str = response.body().string();
        ObjectMapper mapper = new ObjectMapper();
        JsonNode responseNode = mapper.readTree(str);
        JsonNode dataList = responseNode.get("data");
        dataList.forEach(
                data -> {
                    Long categoryId = 0L;
                    Long fileId = 0L;
                    String fileName = "";
                    LocalDateTime timeCreate = null;
                    LocalDateTime gmtModify = null;

                    if (data.has("category")) {
                        categoryId = data.get("category").asLong();
                    }
                    if (data.has("id")) {
                        fileId = data.get("id").asLong();
                    }
                    if (data.has("filename")) {
                        fileName = data.get("filename").asText();
                    }
                    if (data.get("timeCreate").asText() != "") {
                        timeCreate = LocalDateTime.parse(data.get("timeCreate").asText());
                    }
                    if (data.get("gmtModify").asText() != "") {
                        gmtModify = LocalDateTime.parse(data.get("gmtModify").asText());
                    }
                    DatasetAndCategoryDTO fileDTO = DatasetAndCategoryDTO.builder()
                            .datasetId(fileId)
                            .datasetName(fileName)
                            .categoryId(categoryId)
                            .type("kg")
                            .createTime(timeCreate)
                            .gmtModify(gmtModify)
                            .build();
                    if (categoryId != 0 && fileId != 0 && !fileName.isEmpty()) {
                        ret.add(fileDTO);
                    }
                }
        );
        return ret;
    }


    public Long save(DatasetCategoryDTO dcDTO) {
        datasetCategoryMapper.save(dcDTO);
        return dcDTO.getId();
    }

    public Boolean update(DatasetCategoryDTO dcDTO) {
        try {
            datasetCategoryMapper.update(dcDTO);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public JSONObject delete(DatasetCategoryDTO dc) throws Exception {
        JSONObject resData = new JSONObject();
        resData.put("error", 0);

        /* 先判断分类下的数据集中是否有project在用 */
        List<DatasetDTO> datasets = datasetMapper.queryByCategoryId(dc.getId());

        checkDelete(datasets);

        /* 删除分类目录 */
        int res = datasetCategoryMapper.delete(dc);
        if (res > 0) {
            List<String> sqlList = new ArrayList<>();
            for (DatasetDTO dataset : datasets) {
                /* 删除分类目录下的数据集 */
                datasetMapper.delete(dataset);

                /* 生成删除GP中数据集源表sql */
                String json = dataset.getDataJson();
                JSONObject jo = JSONObject.parseObject(json);
                if (jo.getString("type").contains("graph")) {
                    minioService.deleteObject(GRAPH_FILE_DIR, JwtUtil.getCurrentUserDTO().getName() + "/" + jo.getString("table"));
                } else {
                    String schema = jo.getString("schema");
                    String table = jo.getString("table");
                    String newTable = schema + "." + SqlUtil.formatPGSqlColName(table);
                    sqlList.add(String.format(DatabaseConstant.SQL_DROP_TABLE, newTable));
                }
            }

            /* 删除GP中数据集源表 */
            Connection con = null;
            Statement statement = null;
            if (!sqlList.isEmpty()) {
                try {
                    con = gpDataProvider.getConn(DEFAULT_DATASET_ID);
                    con.setAutoCommit(false);
                    statement = con.createStatement();
                    for (String sql : sqlList) {
                        logger.info(sql);
                        statement.addBatch(sql);
                    }
                    statement.executeBatch();
                    con.commit();
//                    gpDataService.executeBatchUpdateSQL(dataSourceKey, sqlList);
                } catch (Exception e) {
                    logger.error(e.getMessage());
                    try {
                        con.rollback();
                    } catch (SQLException e1) {
                        logger.error(e1.getMessage());
                    }
                    throw new DataScienceException(BaseErrorCode.DATASET_GP_DELETE_ERROR);
                } finally {
                    JDBCUtil.close(con, statement, null);
                }
            }
        }

        resData.put("result", true);
        return resData;
    }

    public List<DatasetAndCategoryForProjectVO> queryDatasetCategoryByProjectId(SearchDatasetAndCategoryVO vo) {
        JSONObject data = new JSONObject();
        data.put("userId", JwtUtil.getCurrentUserId());
        data.put("projectId", vo.getProjectId());
        data.put("types", vo.getType().isEmpty() ? Lists.newArrayList() : Arrays.asList(vo.getType().split(",")));
        data.put("content", vo.getContent());

        List<DatasetAndCategoryDTO> datasetCategoryList = datasetCategoryMapper
                .queryDatasetCategoryByProjectId(data);

        return handle(datasetCategoryList);
    }

    public List<DatasetAndCategoryForProjectVO> searchInProject(SearchDatasetAndCategoryVO vo) {
        JSONObject data = new JSONObject();
        data.put("userId", JwtUtil.getCurrentUserId());
        data.put("projectId", vo.getProjectId());
        data.put("types", vo.getType().isEmpty() ? Lists.newArrayList() : Arrays.asList(vo.getType().split(",")));
        data.put("content", vo.getContent());

        List<DatasetAndCategoryDTO> datasetCategoryList = datasetCategoryMapper
                .queryDatasetCategoryByProjectId(data);

        return handle(datasetCategoryList);
    }

    private List<DatasetAndCategoryForProjectVO> handle(List<DatasetAndCategoryDTO> datasetCategoryList) {
        List<DatasetAndCategoryForProjectVO> dcvList = new ArrayList<>();

        if (datasetCategoryList == null || datasetCategoryList.isEmpty()) {
            return dcvList;
        }
        Set<Long> categoryIds = new HashSet<>();
        for (DatasetAndCategoryDTO dc : datasetCategoryList) {
            if (categoryIds.contains(dc.getCategoryId())) {
                for (DatasetAndCategoryForProjectVO dacDto : dcvList) {
                    if (dacDto.getCategoryId().equals(dc.getCategoryId())) {
                        DatasetNameStatusDTO dnDto = new DatasetNameStatusDTO();
                        dnDto.setId(dc.getDatasetId());
                        dnDto.setName(dc.getDatasetName());
                        dnDto.setStatus(dc.getStatus());
                        dnDto.setType(dc.getType());
                        dacDto.getDataset().add(dnDto);
                    }
                }
            } else {
                categoryIds.add(dc.getCategoryId());
                DatasetAndCategoryForProjectVO datasetVo = new DatasetAndCategoryForProjectVO();
                datasetVo.setCategoryId(dc.getCategoryId());
                datasetVo.setCategoryName(dc.getCategoryName());
                datasetVo.setParentId(dc.getParentId());

                List<DatasetNameStatusDTO> dataset = new ArrayList<>();
                DatasetNameStatusDTO dnDto = new DatasetNameStatusDTO();

                if (dc.getDatasetId() != null || !StringUtils.isBlank(dc.getDatasetName())) {
                    dnDto.setId(dc.getDatasetId());
                    dnDto.setName(dc.getDatasetName());
                    dnDto.setStatus(dc.getStatus());
                    dnDto.setType(dc.getType());
                    dataset.add(dnDto);
                }
                datasetVo.setDataset(dataset);
                dcvList.add(datasetVo);
            }
        }
        return dcvList;
    }

    public void checkAuth(long categoryId) {
        long userId = JwtUtil.getCurrentUserId();
        //单纯数据集分类接口仅拥有者可以读写
        if (datasetCategoryMapper.checkAuth(categoryId, userId) == 0) {
            throw DataScienceException.of(BaseErrorCode.UNAUTHORIZED, "数据集分类id:" + categoryId);
        }
    }

    /**
     * 检查数据集使用情况
     *
     * @param datasets
     * @throws Exception
     */
    public void checkDelete(List<DatasetDTO> datasets) throws Exception {
        if (datasets == null) {
            return;
        }

        StringBuffer sb = new StringBuffer();
        for (DatasetDTO dataset : datasets) {
            QueryDatasetUsedInProjectVO vo = new QueryDatasetUsedInProjectVO();
            vo.setDatasetId(dataset.getId());
            vo.setUserId(dataset.getUserId());

            List<DatasetUsedInProjectDTO> dtos = datasetProjectMapper.queryDatasetUsedInProject(vo);

            if (dtos != null && !dtos.isEmpty()) {
                for (DatasetUsedInProjectDTO dto : dtos) {
                    try {
                        Set<Long> users = datasetProjectService
                                .getDatasetUsedInProjectUser(dto.getProjectId(), dataset.getId());
                        if (!users.isEmpty()) {
                            users.remove(JwtUtil.getCurrentUserId());
                            if (users.size() > 0) {
                                sb.append("数据集：").append(dataset.getName())
                                        .append(" 正在被其他用户使用，请先对数据集进行转移!  ");
                            } else {
                                ProjectDTO project = projectService.queryById(dto.getProjectId());
                                sb.append("数据集：").append(dataset.getName()).append(" 在 ")
                                        .append(project.getName()).append(" 项目中使用，请先从项目中移出再删除!  ");
                            }
                        }
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }

        }
        if (StringUtils.isNotBlank(sb)) {
            throw new Exception(sb.toString());
        }
    }

    /**
     * gis用
     * 根据用户id和分类名获取分类id
     *
     * @param userId       用户id
     * @param categoryName 分类名
     * @return
     */
    public Long queryCategoryIdByUserIdAndCategoryName(Long userId, String categoryName) {
        DatasetCategoryDTO dto = new DatasetCategoryDTO();
        dto.setUserId(userId);
        dto.setName(categoryName);
        DatasetCategoryDTO category = datasetCategoryMapper.query(dto);
        return ObjectUtil.isNotNull(category) ? category.getId() : -1L;
    }

}
